<?php

namespace app\api\service;

use app\api\model\UserModel;
use app\lib\enum\ScopeEnum;
use app\lib\exception\TokenException;
use app\lib\exception\WeChatException;
use think\Cache;
use think\Exception;
use think\Model;
/**
 * 微信登录
 * 如果担心频繁被恶意调用，请限制ip
 * 以及访问频率
 */
class UserToken extends Token
{
    protected $code;
    protected $bdLoginUrl;
    protected $bdAppID;
    protected $bdAppSecret;

    function __construct($code)
    {
        //百度小程序那是POST请求 所以需要修改请求方式
        $this->code = $code;
        $this->bdAppID = config('bd_appid');
        $this->bdAppSecret = config('bd_secret');
        $this->bdLoginUrl = 'https://openapi.baidu.com/nalogin/getSessionKeyByCode';
//        $this->bdLoginUrl = sprintf(config('bd.login_url'), $this->bdAppID, $this->bdAppSecret, $this->code);
    }

    /**
     * 微信小程序登录
     * 登陆
     * 思路1：每次调用登录接口都去微信刷新一次session_key，生成新的Token，不删除久的Token
     * 思路2：检查Token有没有过期，没有过期则直接返回当前Token
     * 思路3：重新去微信刷新session_key并删除当前Token，返回新的Token
     *
     * 百度小程序登录
     *
     *
     */
    public function get()
    {
        //准备百度小程序所需要请求的字段
        $postfield = ['code'=>$this->code,'client_id'=>$this->bdAppID,'sk'=>$this->bdAppSecret];
        $result = httpRequest($this->bdLoginUrl,'POST',$postfield);
        // 注意json_decode的第一个参数true
        // 这将使字符串被转化为数组而非对象
        $bdResult = json_decode($result, true);
        if (empty($bdResult)) {
            // 为什么以empty判断是否错误，这是根据微信返回
            // 规则摸索出来的
            // 这种情况通常是由于传入不合法的code (服务器异常不返回客户端)
            return json(msg(0,'','获取session_key及openID时异常，百度内部错误'));
//            throw new Exception('获取session_key及openID时异常，百度内部错误');
        }else {
            // 建议用明确的变量来表示是否成功
            // 微信服务器并不会将错误标记为400，无论成功还是失败都标记成200
            // 这样非常不好判断，只能使用errcode是否存在来判断
            $loginFail = array_key_exists('error_description', $bdResult);
            if ($loginFail) {
//                错误异常封装后易于扩展
                $this->processLoginError($bdResult);
            }
            else {
                // 一切正常 颁发令牌 返回令牌 openid等给客户端
                return $this->grantToken($bdResult);
            }
        }
    }

//    // 判断是否重复获取
//    private function duplicateFetch(){
//       TODO:目前无法简单的判断是否重复获取，还是需要去微信服务器去openid
//       TODO: 这有可能导致失效行为
//    }

    // 处理微信登陆异常
    // 那些异常应该返回客户端，那些异常不应该返回客户端
    // 需要认真思考
    //上述是微信小程序你登录异常

    //目前是处理百度小程序端登录异常
    private function processLoginError($bdResult)
    {
        throw new WeChatException(
            [
                'msg' => $bdResult['error_description'],
                'errorCode' => $bdResult['error']
            ]);
    }

    // 写入缓存
    private function saveToCache($bdResult)
    {
        $key = self::generateToken();
        $value = json_encode($bdResult);
//        $expire_in = config('setting.token_expire_in');
        $result = Cache::store('redis')->set($key,$value,7200);
        if (!$result){
            throw new TokenException([
                'msg' => '服务器缓存异常',
                'errorCode' => 10005
            ]);
        }
        $res=[];
        $res['token']=$key;
        $res['openid']=$bdResult['openid'];
        return $res;
    }

    // 颁发令牌
    // 只要调用登陆就颁发新令牌
    // 但旧的令牌依然可以使用
    // 所以通常令牌的有效时间比较短
    // 目前微信的express_in时间是7200秒
    // 在不设置刷新令牌（refresh_token）的情况下
    // 只能延迟自有token的过期时间超过7200秒（目前还无法确定，在express_in时间到期后
    // 还能否进行微信支付
    // 没有刷新令牌会有一个问题，就是用户的操作有可能会被突然中断

    private function grantToken($bdResult)
    {
        // 通过请求百度接口来获取到的openid来判断该用户是否已存在
        $openid = $bdResult['openid'];
        $user = new UserModel();
        $res = $user->getOpenId($openid);
        if (!$res)
            // 借助百度的openid作为用户标识
            // 但在系统中的相关查询还是使用自己的uid
        {
            //新用户则生成新的数据 返回用户id
            $uid = $this->newUser($openid);
        }
        else {
            //已存在用户返回用户ID
            $uid = $res->id;
        }
//        写入缓存（key:令牌 value:bdResult.uid,scope）
//        $cachedValue = $this->prepareCachedValue($bdResult, $uid);
        // 此处只需要将所需要的数据作为数组缓存起来
        $bdResult['uid'] = $uid;
        $token = $this->saveToCache($bdResult);
        return $token;
    }

//    存储数据
//    private function prepareCachedValue($bdResult, $uid)
//    {
//        $cachedValue = $bdResult;
//        $cachedValue['uid'] = $uid;
////         作用域
////        $cachedValue['scope'] = ScopeEnum::User;
//        return $cachedValue;
//    }

    // 创建新用户
    private function newUser($openid)
    {
        // 有可能会有异常，如果没有特别处理
        // 这里不需要try——catch
        // 全局异常处理会记录日志
        // 并且这样的异常属于服务器异常
        // 也不应该定义BaseException返回到客户端
        $user = UserModel::create(
            [
                'openid' => $openid
            ]);
        return $user->id;
    }
}
